﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "CmdBooleanTunnel.h"
#include "CommandIds.h"
#include "FilePath.h"
#include "GbmpFileSystem.h"
#include "CommandRegister.h"
#include "IMesh.h"
#include "ILine2d.h"
#include "ILine3d.h"
#include "IArc2d.h"
#include "IPolyCurve.h"
#include "IPolygon.h"
#include "ICurve3d.h"
#include "IBody.h"
#include "IDirectShape.h"
#include "AlgorithmMeshOperate.h"
#include "GmBodyBuilder.h"
#include "UiDocumentViewUtils.h"
#include "IUserTransaction.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "IGraphicsMeshBody.h"
#include "UiCommonDialog.h"
#include "IPickFilter.h"
#include "IPickTarget.h"
#include "InteractivePicker.h"
#include "IGraphicsNodeReference.h"
#include "IElementModelShape.h"
#include "IElement.h"
#include "IElementBasicInformation.h"
#include "IPolyCurve3d.h"
#include "ITrimmedSurface.h"
#include "IFace.h"
#include "IGraphicsStyle.h"
#include "IGraphicsStyleData.h"
#include "IGraphicsStyleManager.h"
#include "IGraphicsNodeStyleAndMaterialOverride.h"
#include "IHighlights.h"
#include "IUiView.h"
#include "IDocument.h"
#include "IGraphicsElementShape.h"
#include "IGenericElement.h"
#include <numeric>
#include <map>
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 


using namespace Sample;
using namespace gcmp;

REGISTER_COMMAND(CmdBooleanTunnel)




CmdBooleanTunnel::CmdBooleanTunnel() : CommandBase(ID_CMD_BOOLEAN_TUNNEL)
{

}

CmdBooleanTunnel::~CmdBooleanTunnel()
{

}

bool CmdBooleanTunnel::IsEnabled() const
{
    return UiDocumentViewUtils::GetCurrentDocument() != nullptr;
}

bool CmdBooleanTunnel::IsVisible() const
{
    return UiDocumentViewUtils::GetCurrentDocument() != nullptr;
}
namespace
{
    std::wstring GetMeshBoolErrorCodeToMessage(MeshBoolErrorCode errorCode)
    {
        switch (errorCode)
        {
        case gcmp::MeshBoolErrorCode::BadParameter:
            return L"输入参数有误";
        case gcmp::MeshBoolErrorCode::Success:
            return L"Bool运算成功";
        case gcmp::MeshBoolErrorCode::Unknown:
            return L"未知的情况";
        case gcmp::MeshBoolErrorCode::MeshNullPtr:
            return L"Mesh指针为空";
        case gcmp::MeshBoolErrorCode::BoolOperationError:
            return L"Bool操作类型输入错误";
        case gcmp::MeshBoolErrorCode::DegenerateTriangle:
            return L"存在退化三角形";
        case gcmp::MeshBoolErrorCode::NextEdgeEmpty:
            return L"下一条边为空导致成环失败";
        case gcmp::MeshBoolErrorCode::FailCalcNextEdge:
            return L"当前边和下一条边内外关系错误导致成环失败";
        case gcmp::MeshBoolErrorCode::FailCalcTriangulation:
            return L"三角剖分失败导致成体失败";
        case gcmp::MeshBoolErrorCode::IndexError:
            return L"顶点映射指针为空";
        case gcmp::MeshBoolErrorCode::NotClosed:
            return L"非封闭mesh，有重合顶点";
        case gcmp::MeshBoolErrorCode::FailCalcIntersectLine:
            return L"计算网格交线错误";
        case gcmp::MeshBoolErrorCode::FailCutMesh:
            return L"沿网格交线切割网格错误";
        case gcmp::MeshBoolErrorCode::FailCalssifyMesh:
            return L"对切割后的网格分类错误";
        case gcmp::MeshBoolErrorCode::OtherError:
            return L"其他错误";
        default:
            return L"未知";
        }
    }
}
namespace
{

    class PickElementFilter : public gcmp::IPickFilter
    {
    public:

        virtual bool AllowElement(const gcmp::ElementId& id) const override
        {
            IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pDocument, L"pDocument为空", L"GDMP", L"2023-11-08");
            IGenericElement* pSelElement = quick_cast<IGenericElement>(pDocument->GetElement(id));
            if (pSelElement == nullptr)
                return false;
            IElementBasicInformation* pElementBasicInformation = pSelElement->GetBasicInformation();
            if (pElementBasicInformation == nullptr)
                return false;
            if (pElementBasicInformation->GetCategoryUid() != BuiltInCategoryUniIdentities::BICU_FORM)
                return false;
            return true;
        }

        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& nodeReference) const override
        {
            return true;
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pickTarget) override
        {
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pickTarget, L"pickTarget为空", L"GDMP", L"2023-09-30");
            pickTarget->DisableAll();
            pickTarget->EnableGraphicsElementShape();
            return true;
        }
    };

    //创建隧道body（扫略方式）
    //bodySideLength,sectionSideLength ::创建TunnelBody的相关尺寸
    gcmp::OwnerPtr<gcmp::IBody> CreateTunnelBody(double bodySideLength,double sectionSideLength)
    {
        int length = bodySideLength;
        int width = bodySideLength;
        OwnerPtr<ILine2d> opLine2d1 = ILine2d::Create(Vector2d(-length, width), Vector2d(-length, -width));
        OwnerPtr<ILine2d> opLine2d2 = ILine2d::Create(Vector2d(-length, -width), Vector2d(length, -width));
        OwnerPtr<ILine2d> opLine2d3 = ILine2d::Create(Vector2d(length, -width), Vector2d(length, width));
        std::vector<OwnerPtr<ICurve2d>> curve2ds;
        curve2ds.reserve(4);
        curve2ds.emplace_back(TransferOwnership(opLine2d1));
        curve2ds.emplace_back(TransferOwnership(opLine2d2));
        curve2ds.emplace_back(TransferOwnership(opLine2d3));
        OwnerPtr<IArc2d> opUpArc = IArc2d::Create(Vector2d::Create(0, width), Vector2d::Create(length, width), Vector2d(-length, width), true);
        curve2ds.emplace_back(TransferOwnership(opUpArc));
        OwnerPtr<IPolyCurve> opPolyCurve = IPolyCurve::Create(curve2ds);
        OwnerPtr<IPolygon> opPolygon = IPolygon::Create();
        opPolygon->AddPolyCurveByClone(opPolyCurve.get());
        std::vector<const ICurve3d*> paths;
        OwnerPtr<ILine3d> opLine3d = ILine3d::Create(Vector3d::Create(0, -sectionSideLength, 0), Vector3d::Create(0, sectionSideLength, 0));
        paths.emplace_back(opLine3d.get());
        OwnerPtr<IBody> opSweepBody = GmBodyBuilder::CreateSweepBody(TransferOwnership(opPolygon), paths, Vector3d(0, 0, 1));
        DBG_WARN_AND_RETURN_FALSE_UNLESS(opSweepBody, L"opSweepBody为空", L"GDMP", L"2023-09-30");
        return TransferOwnership(opSweepBody);
    }

}

gcmp::OwnerPtr<gcmp::IAction> CmdBooleanTunnel::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    IDocument* pDocument = UiDocumentViewUtils::GetCurrentDocument();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pDocument, L"pDocument为空", L"GDMP", L"2023-09-30");

    //1.拾取mesh对象
    const bool isMultiSelect = false;
    const bool isPickPointSelection = true;
    const std::wstring mouseTooltip = L"选择MeshBody对象,尝试从MeshBody侧面中间附近挖个隧道";
    const std::wstring statusBarTooltip = L"选择MeshBody对象,尝试从MeshBody侧面中间附近挖个隧道";
    Vector3d mousePoint;
    GraphicsNodeReferenceOwnerPtrVector allSelections;
    OwnerPtr<PickElementFilter> pickfilter(NEW_AS_OWNER_PTR(PickElementFilter));
    auto pickResult = InteractivePicker::Select(isMultiSelect, isPickPointSelection, TransferOwnership(pickfilter), mouseTooltip, statusBarTooltip, mousePoint, allSelections);
    if (pickResult.FinishStatus != InteractivePicker::PickerFinishStatus::Successful || allSelections.size() == 0)
        return nullptr;

    //2.统计mesh对象的最大boundingBox,用于计算需要挖的隧道的尺寸和位置,并记录mesh对象的mesh数据用于布尔计算
    Vector3d maxPoint = Vector3d::Create(-Constants::MAX_MODEL_SPACE_SIZE, -Constants::MAX_MODEL_SPACE_SIZE, -Constants::MAX_MODEL_SPACE_SIZE);//用于记录整个大地的最大位置点
    Vector3d minPoint = Vector3d::Create(Constants::MAX_MODEL_SPACE_SIZE, Constants::MAX_MODEL_SPACE_SIZE, Constants::MAX_MODEL_SPACE_SIZE);;//用于记录整个大地的最小位置点  
    auto&& opNodeRefOwner = allSelections[0];
    if (opNodeRefOwner == nullptr) return nullptr;
    IElement* pSelElement = pDocument->GetElement(opNodeRefOwner->GetElementId());
    if (pSelElement == nullptr) return nullptr;
    gcmp::IElementModelShape* pModelShape = pSelElement->GetElementModelShape();
    if (pModelShape == nullptr)
        return nullptr;
    const gcmp::IGraphicsElementShape* pElementShape = pModelShape->GetGraphicsElementShape();
    if (pElementShape == nullptr) return nullptr;
    const std::vector<OwnerPtr<IGraphicsNode>>& nodes = pElementShape->GetChildren();
    const IMesh* pSelMesh;
    for (auto&& graphicsNode : nodes)
    {
        if (const IGraphicsMeshBody* graphicsMeshBody = quick_cast<IGraphicsMeshBody>(graphicsNode.get()))
        {
            pSelMesh = graphicsMeshBody->GetMesh();
            if (pSelMesh == nullptr || !pSelMesh->IsClosed()) //非闭合mesh不支持布尔运算
                continue;
            if (pSelMesh->GetBox().GetMaxPoint().GetZ() > maxPoint.GetZ())
                maxPoint = pSelMesh->GetBox().GetMaxPoint();
            if (pSelMesh->GetBox().GetMaxPoint().GetZ() < minPoint.GetZ())
                minPoint = pSelMesh->GetBox().GetMinPoint();
            break;
        }
    }
    if (pSelMesh == nullptr)
        return nullptr;

    //3.创建隧道body（扫略方法）    
    double sectionSideLength = (maxPoint.GetZ() - minPoint.GetZ()) / 10;
    double tunnelLength = (maxPoint.GetY() - minPoint.GetY()) * 1.5;
    OwnerPtr<IBody> opTunnelBody = CreateTunnelBody(sectionSideLength, tunnelLength);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opTunnelBody, L"opSweepBody为空", L"GDMP", L"2023-09-30");
    Matrix4d translate;
    Vector3d translateVector = 0.5 * (maxPoint + minPoint) + Vector3d((maxPoint.X() - minPoint.X()) / 4, 0, 0);//平移到大地中间再往X移动(maxPoint.X()-minPoint.X())/4
    translate.MakeTranslate(translateVector);
    opTunnelBody->Transform(translate);

    //4.利用隧道body创建mesh用于布尔运算
    OwnerPtr<gcmp::IMesh> opSweepMesh;
    double edgeLengthRatio = 0.0;
    MeshFromBrepBodyErrorCode meshErrorCode = AlgorithmMeshOperate::CreateMeshFromBrepBody(opTunnelBody.get(), 2, 1.5, edgeLengthRatio, gcmp::DiscreteEpsilonType::User, opSweepMesh);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opSweepMesh && meshErrorCode == MeshFromBrepBodyErrorCode::Success, L"opSweepMesh创建失败", L"GDMP", L"2023-09-30");

    //5.进行布尔运算扣减，将隧道挖出， 并显示结果
    OwnerPtr<IUserTransaction> opUserTransaction = IUserTransaction::Create(pDocument, GBMP_TR(L"创建Mesh"));
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opUserTransaction, L"opUserTransaction创建失败", L"GDMP", L"2023-09-30");
    OwnerPtr<IMesh> opSubtractMesh;
    MeshBoolErrorCode boolErrorCode = AlgorithmMeshOperate::MeshBoolSubtract(pSelMesh, opSweepMesh.get(), opSubtractMesh);
    if (!opSubtractMesh || boolErrorCode != MeshBoolErrorCode::Success)
    {
        std::wstring errorMessage = GetMeshBoolErrorCodeToMessage(boolErrorCode);
        UiCommonDialog::ShowMessageBox(L"提示", gcmp::StringUtil::FormatWString(L"创建隧道失败,mesh布尔运算失败:%s.", errorMessage.c_str()), (int)UiCommonDialog::ButtonType::Yes);
        return  nullptr;
    }

    gcmp::OwnerPtr<IGraphicsElementShape> opShape = IGraphicsElementShape::Create(gcmp::GraphicsRenderLayer::Model);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opShape, L"opShape创建失败", L"GDMP", L"2023-09-30");
    opShape->AddChild(IGraphicsMeshBody::Create(TransferOwnership(opSubtractMesh)));
    pModelShape->SetGraphicsElementShape(TransferOwnership(opShape));
    opUserTransaction->Commit();

    return nullptr;
}